/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.log4j.lf5.util;

import org.apache.log4j.lf5.Log4JLogRecord;
import org.apache.log4j.lf5.LogLevel;
import org.apache.log4j.lf5.LogLevelFormatException;
import org.apache.log4j.lf5.LogRecord;
import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
import org.apache.log4j.lf5.viewer.LogFactor5ErrorDialog;
import org.apache.log4j.lf5.viewer.LogFactor5LoadingDialog;

import javax.swing.*;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Provides utility methods for input and output streams.
 * 
 * @author Brad Marlborough
 * @author Richard Hurst
 */

// Contributed by ThoughtWorks Inc.
public class LogFileParser implements Runnable {
	//--------------------------------------------------------------------------
	// Constants:
	//--------------------------------------------------------------------------
	public static final String RECORD_DELIMITER = "[slf5s.start]";
	public static final String ATTRIBUTE_DELIMITER = "[slf5s.";
	public static final String DATE_DELIMITER = ATTRIBUTE_DELIMITER + "DATE]";
	public static final String THREAD_DELIMITER = ATTRIBUTE_DELIMITER
			+ "THREAD]";
	public static final String CATEGORY_DELIMITER = ATTRIBUTE_DELIMITER
			+ "CATEGORY]";
	public static final String LOCATION_DELIMITER = ATTRIBUTE_DELIMITER
			+ "LOCATION]";
	public static final String MESSAGE_DELIMITER = ATTRIBUTE_DELIMITER
			+ "MESSAGE]";
	public static final String PRIORITY_DELIMITER = ATTRIBUTE_DELIMITER
			+ "PRIORITY]";
	public static final String NDC_DELIMITER = ATTRIBUTE_DELIMITER + "NDC]";

	//--------------------------------------------------------------------------
	// Protected Variables:
	//--------------------------------------------------------------------------

	//--------------------------------------------------------------------------
	// Private Variables:
	//--------------------------------------------------------------------------
	private static SimpleDateFormat _sdf = new SimpleDateFormat(
			"dd MMM yyyy HH:mm:ss,S");
	private LogBrokerMonitor _monitor;
	LogFactor5LoadingDialog _loadDialog;
	private InputStream _in = null;

	//--------------------------------------------------------------------------
	// Constructors:
	//--------------------------------------------------------------------------
	public LogFileParser(File file) throws IOException, FileNotFoundException {
		this(new FileInputStream(file));
	}

	public LogFileParser(InputStream stream) throws IOException {
		_in = stream;
	}
	//--------------------------------------------------------------------------
	// Public Methods:
	//--------------------------------------------------------------------------

	/**
	 * Starts a new thread to parse the log file and create a LogRecord. See
	 * run().
	 * 
	 * @param monitor
	 *            LogBrokerMonitor
	 */
	public void parse(LogBrokerMonitor monitor) throws RuntimeException {
		_monitor = monitor;
		Thread t = new Thread(this);
		t.start();
	}

	/**
	 * Parses the file and creates new log records and adds the record to the
	 * monitor.
	 */
	public void run() {

		int index = 0;
		int counter = 0;
		LogRecord temp;
		boolean isLogFile = false;

		_loadDialog = new LogFactor5LoadingDialog(_monitor.getBaseFrame(),
				"Loading file...");

		try {
			String logRecords = loadLogFile(_in);

			while ((counter = logRecords.indexOf(RECORD_DELIMITER, index)) != -1) {
				temp = createLogRecord(logRecords.substring(index, counter));
				isLogFile = true;

				if (temp != null) {
					_monitor.addMessage(temp);
				}

				index = counter + RECORD_DELIMITER.length();
			}

			if (index < logRecords.length() && isLogFile) {
				temp = createLogRecord(logRecords.substring(index));

				if (temp != null) {
					_monitor.addMessage(temp);
				}
			}

			if (isLogFile == false) {
				throw new RuntimeException("Invalid log file format");
			}
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					destroyDialog();
				}
			});

		} catch (RuntimeException e) {
			destroyDialog();
			displayError("Error - Invalid log file format.\nPlease see documentation"
					+ " on how to load log files.");
		} catch (IOException e) {
			destroyDialog();
			displayError("Error - Unable to load log file!");
		}

		_in = null;
	}

	//--------------------------------------------------------------------------
	// Protected Methods:
	//--------------------------------------------------------------------------
	protected void displayError(String message) {
		LogFactor5ErrorDialog error = new LogFactor5ErrorDialog(_monitor
				.getBaseFrame(), message);

	}

	//--------------------------------------------------------------------------
	// Private Methods:
	//--------------------------------------------------------------------------
	private void destroyDialog() {
		_loadDialog.hide();
		_loadDialog.dispose();
	}

	/**
	 * Loads a log file from a web server into the LogFactor5 GUI.
	 */
	private String loadLogFile(InputStream stream) throws IOException {
		BufferedInputStream br = new BufferedInputStream(stream);

		int count = 0;
		int size = br.available();

		StringBuffer sb = null;
		if (size > 0) {
			sb = new StringBuffer(size);
		} else {
			sb = new StringBuffer(1024);
		}

		while ((count = br.read()) != -1) {
			sb.append((char) count);
		}

		br.close();
		br = null;
		return sb.toString();

	}

	private String parseAttribute(String name, String record) {

		int index = record.indexOf(name);

		if (index == -1) {
			return null;
		}

		return getAttribute(index, record);
	}

	private long parseDate(String record) {
		try {
			String s = parseAttribute(DATE_DELIMITER, record);

			if (s == null) {
				return 0;
			}

			Date d = _sdf.parse(s);

			return d.getTime();
		} catch (ParseException e) {
			return 0;
		}
	}

	private LogLevel parsePriority(String record) {
		String temp = parseAttribute(PRIORITY_DELIMITER, record);

		if (temp != null) {
			try {
				return LogLevel.valueOf(temp);
			} catch (LogLevelFormatException e) {
				return LogLevel.DEBUG;
			}

		}

		return LogLevel.DEBUG;
	}

	private String parseThread(String record) {
		return parseAttribute(THREAD_DELIMITER, record);
	}

	private String parseCategory(String record) {
		return parseAttribute(CATEGORY_DELIMITER, record);
	}

	private String parseLocation(String record) {
		return parseAttribute(LOCATION_DELIMITER, record);
	}

	private String parseMessage(String record) {
		return parseAttribute(MESSAGE_DELIMITER, record);
	}

	private String parseNDC(String record) {
		return parseAttribute(NDC_DELIMITER, record);
	}

	private String parseThrowable(String record) {
		return getAttribute(record.length(), record);
	}

	private LogRecord createLogRecord(String record) {
		if (record == null || record.trim().length() == 0) {
			return null;
		}

		LogRecord lr = new Log4JLogRecord();
		lr.setMillis(parseDate(record));
		lr.setLevel(parsePriority(record));
		lr.setCategory(parseCategory(record));
		lr.setLocation(parseLocation(record));
		lr.setThreadDescription(parseThread(record));
		lr.setNDC(parseNDC(record));
		lr.setMessage(parseMessage(record));
		lr.setThrownStackTrace(parseThrowable(record));

		return lr;
	}

	private String getAttribute(int index, String record) {
		int start = record.lastIndexOf(ATTRIBUTE_DELIMITER, index - 1);

		if (start == -1) {
			return record.substring(0, index);
		}

		start = record.indexOf("]", start);

		return record.substring(start + 1, index).trim();
	}
	//--------------------------------------------------------------------------
	// Nested Top-Level Classes or Interfaces
	//--------------------------------------------------------------------------

}
